package com.gitee.wsl.unit.angle

import com.gitee.wsl.ext.base.ifValue
import com.gitee.wsl.math.Math
import com.gitee.wsl.mathematics.interpolator.ext.FULL
import com.gitee.wsl.mathematics.interpolator.ext.HALF
import com.gitee.wsl.unit.BaseUnit
import com.gitee.wsl.unit.UnitType
import com.gitee.wsl.unit.angle.ext.plus
import com.gitee.wsl.unit.angle.ext.unaryMinus
import kotlin.jvm.JvmStatic
import kotlin.math.absoluteValue


sealed interface AngleUnitType: UnitType<Double> {

    data object DEG: AngleUnitType {
        override val baseUnit: Double = 1.0
    }

    data object RAD: AngleUnitType {
        override val baseUnit: Double =  57.2958
    }

    //分
    data object MINUTE: AngleUnitType {
        override val baseUnit: Double = 0.0167
    }

    //秒
    data object SECOND: AngleUnitType {
        override val baseUnit: Double = 0.00028
    }

    //毫弧度
    data object MRAD: AngleUnitType {
        override val baseUnit: Double = 0.0573
    }

    //百分度
    data object GON: AngleUnitType {
        override val baseUnit: Double = 0.0573
    }

}


sealed class AngleUnit( value:Double, type: AngleUnitType = AngleUnitType.DEG): BaseUnit<Double, AngleUnitType>(value, type){

    val isRadian:Boolean get() = type == AngleUnitType.RAD

    val isDegrees:Boolean get() = type == AngleUnitType.DEG

    val absoluteValue: AngleUnit get() = Radians(radian.absoluteValue)

    override fun toBase() = value * type.baseUnit

    val radian:Double get() = toRadians().value

    val degrees:Double get() = toDegrees().value

    val radianF get() = radian.toFloat()

    val degreesF get() = degrees.toFloat()

    fun toRadians() = (isRadian).ifValue(this,convert(baseByte = toBase(),AngleUnitType.RAD))

    fun toDegrees() = (isRadian).ifValue(convert(baseByte = toBase(),AngleUnitType.DEG),this)

    //override fun compareTo(other: AngleUnit): Int = toBase().compareTo(other.toBase())

    val normalized: AngleUnit
        get() {
            val rad = radian
            val twoPiRad = FULL.radian
            return if (rad >= 0)
                Radians(rad % twoPiRad)
            else
                Radians((rad % twoPiRad) + twoPiRad)
        }

    /** Normalize between -.5..+.5  ... -PI..+PI.radians ... -180..+180.degrees */
    val normalizedHalf: AngleUnit get() {
        val res = normalized
        return if (res > AngleUnit.HALF) - AngleUnit.FULL + res else res
    }

    class Degrees(value:Double): AngleUnit(value, AngleUnitType.DEG){
        constructor(value:Number):this(value.toDouble())
        override fun format(): String ="$value DEG"
    }

    class Radians(value:Double): AngleUnit(value, AngleUnitType.RAD){
        constructor(value:Number):this(value.toDouble())
        override fun format(): String ="$value RAD"
    }

    class Minute(value:Double): AngleUnit(value, AngleUnitType.MINUTE){
        constructor(value:Number):this(value.toDouble())
        override fun format(): String ="$value Minute"
    }

    class Second(value:Double): AngleUnit(value, AngleUnitType.SECOND){
        constructor(value:Number):this(value.toDouble())
        override fun format(): String ="$value Second"
    }

    class MRad(value:Double): AngleUnit(value, AngleUnitType.MRAD){
        constructor(value:Number):this(value.toDouble())
        override fun format(): String ="$value MRAD"
    }

    class Gon(value:Double): AngleUnit(value, AngleUnitType.GON){
        constructor(value:Number):this(value.toDouble())
        override fun format(): String ="$value GON"
    }

    override fun convertTo(desType: AngleUnitType): AngleUnit{
        if(this.type == desType) return this
        return convert(toBase(),desType)
    }

    private fun convert(baseByte:Double,desType: AngleUnitType): AngleUnit {
        val newValue = baseByte / desType.baseUnit
        return when (desType) {
            AngleUnitType.DEG -> Degrees(newValue)
            AngleUnitType.RAD -> Radians(newValue)
            AngleUnitType.MINUTE -> Minute(newValue)
            AngleUnitType.SECOND -> Second(newValue)
            AngleUnitType.GON -> Gon(newValue)
            AngleUnitType.MRAD -> MRad(newValue)
        }
    }

    companion object{
//        fun convert(sourceCount: AngleUnit, desType: AngleUnitType): AngleUnit {
//            return sourceCount.convertTo(desType)
//        }

       val ZERO = Degrees(0.0)
       //val QUARTER_PI by lazy { Degrees(45.0) }
       //val HALF_PI by lazy { Degrees(90.0) }
       val PI = Degrees(180.0)
       val TWO_PI = Degrees(360.0)

        /** Represents a right angle of negative 90 degrees  */
        @JvmStatic val NEG90 = (-90.0).toDegrees()
        /** Represents an angle of negative 180 degrees  */
        @JvmStatic val NEG180 = (-180.0).toDegrees()
        /** Represents an angle of negative 360 degrees  */
        @JvmStatic val NEG360 = (-360.0).toDegrees()

        //
        // Compass
        //
        val  N = 0.deg
        val  North = N
        val  NbE = 11.25.deg
        val  NNE = 22.50.deg
        val  NEbN = 33.75.deg
        val  NE = 45.deg
        val  NEbE = 56.25.deg
        val  ENE = 67.50.deg
        val  EbN = 78.75.deg
        val  E = 90.deg
        val  East = E;
        val  EbS = 101.25.deg
        val  ESE = 112.50.deg
        val  SEbE = 123.75.deg
        val  SE = 135.deg
        val  SEbS = 146.25.deg
        val  SSE = 157.50.deg
        val  SbE = 168.75.deg
        val  S = 180.deg
        val  South = S;
        val  SbW = 191.25.deg
        val  SSW = 202.50.deg
        val  SWbS = 213.75.deg
        val  SW = 225.deg
        val  SWbW = 236.25.deg
        val  WSW = 247.50.deg
        val  WbS = 258.75.deg
        val  W = 270.deg
        val  West = W;
        val  WbN = 281.25.deg
        val  WNW = 292.50.deg
        val  NWbW = 303.75.deg
        val  NW = 315.deg
        val  NWbN = 326.25.deg
        val  NNW = 337.50.deg
        val  NbW = 348.75.deg
        
        /**
         * Conversion factor for degrees to radians.
         */
        val DEGREES_TO_RADIANS = Math.PI / 180.0
        /**
         * Conversion factor for radians to degrees.
         */
        const val RADIANS_TO_DEGREES = 180.0 / Math.PI
        /**
         * Conversion factor for degrees to minutes.
         */
        const val DEGREES_TO_MINUTES = 60.0
        /**
         * Conversion factor for minutes to degrees.
         */
        const val MINUTES_TO_DEGREES = 1.0 / 60.0
        /**
         * Conversion factor for degrees to seconds.
         */
        const val DEGREES_TO_SECONDS = 3600.0
        /**
         * Conversion factor for seconds to degrees.
         */
        const val SECONDS_TO_DEGREES = 1.0 / 3600.0
        /**
         * Conversion factor for degrees to milliradians.
         */
        const val DEGREES_TO_MRAD = Math.PI * 2.0 / 360.0
        /**
         * Conversion factor for milliradians to degrees.
         */
        const val MRAD_TO_DEGREES = 360.0 / Math.PI / 2.0
        /**
         * Conversion factor for degrees to milliradians (NATO).
         */
        const val DEGREES_TO_MIL_NATO = 6400.0 / 360.0
        /**
         * Conversion factor for milliradians (NATO) to degrees.
         */
        const val MIL_NATO_TO_DEGREES = 360.0 / 6400.0
        /**
         * Conversion factor for degrees to milliradians (Warsaw Pact).
         */
        const val DEGREES_TO_MIL_WP = 6000.0 / 360.0
        /**
         * Conversion factor for milliradians (Warsaw Pact) to degrees.
         */
        const val MIL_WP_TO_DEGREES = 360.0 / 6000.0
        /**
         * Conversion factor for degrees to milliradians (Sweden).
         */
        const val DEGREES_TO_STRECK = 6300.0 / 360.0
        /**
         * Conversion factor for milliradians (Sweden) to degrees.
         */
        const val STRECK_TO_DEGREES = 360.0 / 6000.0
        /**
         * Conversion factor for degrees to minute of angle (MOA).
         */
        const val DEGREES_TO_MOA = 21600.0 / 360.0
        /**
         * Conversion factor for minute of angle (MOA) to degrees.
         */
        const val MOA_TO_DEGREES = 360.0 / 21600.0

        /**
         * Obtains an angle from a specified number of degrees.
         *
         * @param degrees the size in degrees of the angle to be obtained
         *
         * @return a new angle, whose size in degrees is given by [degrees]
         */
        @JvmStatic fun fromDegrees(degrees: Double) = Degrees(degrees)

        /**
         * Obtains an angle from a specified number of radians.
         *
         * @param radians the size in radians of the angle to be obtained.
         *
         * @return a new angle, whose size in radians is given by [radians].
         */
        @JvmStatic fun fromRadians(radians: Double) = Radians(radians)


    }

}

fun Int.toDegrees() = AngleUnit.Degrees(this.toDouble())

fun Float.toDegrees() = AngleUnit.Degrees(this.toDouble())

fun Double.toDegrees()  = AngleUnit.Degrees(this)

fun Float.toRadians() = AngleUnit.Radians(this.toDouble())

fun Double.toRadians() = AngleUnit.Radians(this)

fun Number.toDegrees()  = AngleUnit.Degrees(this)

fun Number.toRadians()= AngleUnit.Radians(this.toDouble())

inline val Float.rad: AngleUnit.Radians get() = AngleUnit.Radians(this.toDouble())
inline val Double.rad: AngleUnit.Radians get() = AngleUnit.Radians(this)
inline val Float.deg: AngleUnit.Degrees get() = AngleUnit.Degrees(this.toDouble())
inline val Double.deg: AngleUnit.Degrees get() = AngleUnit.Degrees(this)
inline val Int.deg: AngleUnit.Degrees get() = AngleUnit.Degrees(this.toDouble())



